package bsh;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.asm.acessor.FieldObjectAcessor;
import bsh.asm.acessor.ObjectAccessor;
import bsh.asm.acessor.StaticFieldObjectAccessor;
import bsh.asm.acessor.ThisFieldObjectAcessor;
import bsh.compilation.CompilationOptions;
import bsh.exception.CompilationFailedException;
import bsh.meta.FieldDescriptor;
import bsh.meta.InnerTypeGlobalScope;
import bsh.meta.JavaClassWrapper;
import bsh.meta.MethodDescriptor;
import bsh.meta.Scope;
import bsh.meta.ScopeImpl;
import bsh.meta.TypeWrapper;
import bsh.meta.UnfinishedInnerType;
import bsh.meta.UnfinishedInnerTypeImpl;
import bsh.meta.UnfinishedType;
import bsh.meta.UnfinishedTypeImpl;

/**
 * Impl de {@link CompilationUnit}
 * 
 * @author takeshi
 */
public class CompilationUnitImpl implements CompilationUnit {

    private final UnfinishedType thisType;

    private final Map<SimpleNode, NodeCompiler> compilers = new IdentityHashMap<SimpleNode, NodeCompiler>();

    private final CompilerManagerInternals compiler;

    private final ScopeImpl globalScope;

    private final CompilationOptions compilationOptions;

    private final Map<String, byte[]> innerClasses = new HashMap<String, byte[]>();

    private final CompilationUnit outerClass;

    /**
     * Private constructor to create inner types
     * 
     * @param sourceCodeName the sourceCodeName
     * @param clName the className
     * @param superType the superType
     * @param ifs the declared interfaces
     * @param outerClass the outerClass
     * @param declaringScope the declaring scope
     * @param modifiers the class modifiers
     * @param simpleName the simpleName of the class. May be null if annonymous
     */
    private CompilationUnitImpl(String sourceCodeName, String clName, TypeWrapper superType, List<? extends TypeWrapper> ifs,
            CompilationUnit outerClass, Scope declaringScope, int modifiers, String simpleName) {
        super();
        this.outerClass = outerClass;
        this.thisType =
                new UnfinishedInnerTypeImpl(sourceCodeName, clName, superType, ifs, outerClass.thisType(), declaringScope, modifiers,
                        simpleName);
        this.compiler = outerClass.getCompilerManager();
        this.compilationOptions = outerClass.options();
        // this.typeResolver = new CachedTypeResolver(outerClass);
        this.globalScope = new InnerTypeGlobalScope(declaringScope, (UnfinishedInnerType) this.thisType);
        addSuperClassFieldAccessors(thisType.superType());
    }

    /**
     * C'tor
     * 
     * @param sourceCodeName {@link String}
     * @param clName o nome da classe
     * @param superType o tupertype
     * @param compiler o {@link CompilerManagerInternals} que criou este {@link CompilationUnit}
     * @param options {@link CompilationOptions}
     */
    public CompilationUnitImpl(String sourceCodeName, String clName, TypeWrapper superType, CompilerManagerInternals compiler,
            CompilationOptions options) {
        this(sourceCodeName, clName, superType, compiler, options, Opcodes.ACC_PUBLIC);
    }

    /**
     * Constructor
     * 
     * @param sourceCodeName {@link String}
     * @param clName the classname
     * @param superType the supertype
     * @param compiler the {@link CompilerManagerInternals} that is used to compile this {@link CompilationUnit}
     * @param options the {@link CompilationOptions}
     * @param modifiers the class declaration modifiers
     */
    public CompilationUnitImpl(String sourceCodeName, String clName, TypeWrapper superType, CompilerManagerInternals compiler,
            CompilationOptions options, int modifiers) {
        this.outerClass = null;
        this.thisType = new UnfinishedTypeImpl(sourceCodeName, clName, superType, Collections.<TypeWrapper> emptyList(), modifiers);
        this.compiler = compiler;
        this.globalScope = new ScopeImpl();
        // this.typeResolver = new CachedTypeResolver(this.compiler == null ? new ReflectionUtilsImpl() : this.compiler.reflectionUtils());
        this.compilationOptions = options;
        Collection<? extends FieldDescriptor> fields = thisType.visibleFields();
        for (FieldDescriptor fieldDescriptor : fields) {
            this.globalScope.addAccessor(fieldDescriptor.fieldName(), this.newFieldAccessor(fieldDescriptor));
        }
        addSuperClassFieldAccessors(thisType.superType());
    }

    /**
     * Constructor
     * 
     * @param sourceCodeName the source code name
     * @param clName the name of the class
     * @param compilerImpl the {@link Compiler}
     * @param options the {@link CompilationOptions}
     * @param classModifiers int
     * @param unfinishedType {@link UnfinishedType}
     */
    public CompilationUnitImpl(String sourceCodeName, String clName, CompilerImpl compilerImpl, CompilationOptions options,
            int classModifiers, UnfinishedType unfinishedType) {
        this.outerClass = null;
        this.thisType = unfinishedType;
        this.compiler = compilerImpl;
        this.globalScope = new ScopeImpl();
        this.compilationOptions = options;
        Collection<? extends FieldDescriptor> fields = thisType.visibleFields();
        for (FieldDescriptor fieldDescriptor : fields) {
            this.globalScope.addAccessor(fieldDescriptor.fieldName(), this.newFieldAccessor(fieldDescriptor));
        }
        addSuperClassFieldAccessors(thisType.superType());
    }

    /**
     * {@inheritDoc}
     */
    public CompilationUnit createInnerType(String clName, TypeWrapper superType, List<? extends TypeWrapper> ifs, Scope declaringScope,
            int modifiers, String simpleName) {
        return new CompilationUnitImpl(this.thisType.getSourceCodeFileName(), clName, superType, ifs, this, declaringScope, modifiers,
                simpleName);
    }

    /**
     * Adds accessor for fields visible from the super class
     * 
     * @param superType {@link TypeWrapper}
     */
    private void addSuperClassFieldAccessors(TypeWrapper superType) {
        if (superType != null && !JavaClassWrapper.OBJECT_WRAPPER.equals(superType)) {
            for (FieldDescriptor fd : superType.visibleFields()) {
                if (this.globalScope.getVariable(fd.fieldName()) == null) {
                    this.globalScope.addAccessor(fd.fieldName(), newFieldAccessor(fd));
                }
            }
            for (FieldDescriptor fd : superType.protectedFields()) {
                if (this.globalScope.getVariable(fd.fieldName()) == null) {
                    this.globalScope.addAccessor(fd.fieldName(), newFieldAccessor(fd));
                }
            }
            addSuperClassFieldAccessors(superType.superType());
            for (TypeWrapper tw : superType.interfaces()) {
                addSuperClassFieldAccessors(tw);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor getField(String name) {
        return this.thisType.getField(name);
    }

    /**
     * {@inheritDoc}
     */
    public NodeCompiler getNodeCompiler(SimpleNode node) throws CompilationFailedException {
        NodeCompiler cached = this.compilers.get(node);
        if (cached != null) {
            return cached;
        }
        cached = this.compiler.getNodeCompiler(node, this);
        this.compilers.put(node, cached);
        return cached;
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper getNodeType(SimpleNode node, Scope scope) throws CompilationFailedException {
        if (node instanceof BSHType) {
            return TypedVariableDeclarationCompiler.resolveTypeForNode((BSHType) node, scope);
        }
        NodeCompiler nodeCompiler = this.getNodeCompiler(node);
        if (nodeCompiler != null) {
            return nodeCompiler.nodeResultType(scope);
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public String getThisClassDescriptor() {
        return this.thisType.internalName();
    }

    /**
     * {@inheritDoc}
     */
    public ReflectionUtils reflectionUtils() {
        return this.getCompilerManager().reflectionUtils();
    }

    /**
     * Tenta resolver uma classe
     * 
     * @param name String
     * @return {@link TypeWrapper}
     */
    public TypeWrapper resolveClass(String name) {
        return this.reflectionUtils().resolveType(name);
    }

    /**
     * {@inheritDoc}
     */
    public UnfinishedType thisType() {
        return this.thisType;
    }

    /**
     * {@inheritDoc}
     */
    public CompilerManagerInternals getCompilerManager() {
        return this.compiler;
    }

    /**
     * {@inheritDoc}
     */
    public ObjectAccessor getFieldAcessor(String string) {
        ObjectAccessor accessor = this.globalScope.getAccessor(string);
        if (accessor == null) {
            FieldDescriptor fieldDescriptor = this.thisType.getField(string);
            if (fieldDescriptor != null) {
                accessor =
                        fieldDescriptor.isStatic() ? new StaticFieldObjectAccessor(thisType, new FieldObjectAcessor(fieldDescriptor))
                                : new ThisFieldObjectAcessor(thisType, fieldDescriptor);
                this.globalScope.addAccessor(string, accessor);
            }
        }
        return accessor;
    }

    /**
     * Devolve um {@link ThisFieldObjectAcessor}
     * 
     * @param fieldDescriptor {@link FieldDescriptor}
     * @return {@link ThisFieldObjectAcessor}
     */
    private ObjectAccessor newFieldAccessor(FieldDescriptor fieldDescriptor) {
        if (fieldDescriptor.isStatic()) {
            FieldObjectAcessor f = new FieldObjectAcessor(fieldDescriptor);
            return new StaticFieldObjectAccessor(fieldDescriptor.declaringType(), f);
        } else {
            return new ThisFieldObjectAcessor(this.thisType, fieldDescriptor);
        }
    }

    /**
     * {@inheritDoc}
     */
    public ScopeImpl compilationUnitScope() {
        return this.globalScope;
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addField(String name, TypeWrapper type, int modifiers) {
        if ((Opcodes.ACC_STATIC & modifiers) != 0) {
            return addStaticField(name, type, modifiers);
        } else {
            FieldDescriptor fd = this.thisType.addField(name, type, modifiers);
            this.globalScope.addAccessor(name, this.newFieldAccessor(fd));
            return fd;
        }
    }

    /**
     * {@inheritDoc}
     */
    public void compileNode(SimpleNode child, MethodVisitor visitor, CompilationState state) throws CompilationFailedException {
        this.getNodeCompiler(child).compile(visitor, state);
    }

    /**
     * {@inheritDoc}
     */
    public void addMethod(MethodDescriptor methodDescriptor) {
        this.thisType.addMethod(methodDescriptor);
    }

    /**
     * {@inheritDoc}
     */
    public CompilationOptions options() {
        return this.compilationOptions;
    }

    /**
     * {@inheritDoc}
     */
    public FieldDescriptor addStaticField(String name, TypeWrapper type, int options) {
        FieldDescriptor fd = this.thisType.addStaticField(name, type, options);
        this.globalScope.addAccessor(name, new StaticFieldObjectAccessor(this.thisType, new FieldObjectAcessor(fd)));
        return fd;
    }

    /**
     * {@inheritDoc}
     */
    public void addInnerClassDefinition(byte[] data, UnfinishedInnerType innerType) {
        this.thisType.addInnerType(innerType);
        if (this.outerClass != null) {
            this.outerClass.addInnerClassDefinition(data, innerType);
        } else {
            this.innerClasses.put(innerType.className(), data);
        }
    }

    /**
     * @return the globalScope
     */
    public ScopeImpl getGlobalScope() {
        return globalScope;
    }

    /**
     * {@inheritDoc}
     */
    public Map<String, byte[]> getDependentTypes() {
        return new HashMap<String, byte[]>(this.innerClasses);
    }
}
